#include "client_pico.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <ev.h>

#define SERVER_IP "192.168.43.230" // 服务器 IP，替换为实际值
#define CLIENT_IP "192.168.35.212"
#define CLIENT_IP_WIFI "192.168.230.26"
#define CLIENT_IF_NAME "enp2s0"
#define CLIENT_IF_NAME_WIFI "wlp3s0"

#define SERVER_PORT 6015
#define CLIENT_PORT 6014
#define CLIENT_PORT_WIFI 6013
#define BUFFER_SIZE 1536



struct ClientContext {

    int sockfd;
    struct sockaddr_in server_addr;
    struct sockaddr_in from_addr;
    struct sockaddr_in local_addr;

    ev_io io_watcher;
    ev_timer timer_watcher;
    picoquic_quic_t* quic = NULL;
    picoquic_cnx_t* cnx = NULL;
    uint64_t stream_id = 0; //默认0,客户端的双向流
    picoquic_call_back_event_t state;
    
};
char const* ticket_store_filename = NULL;
char const* token_store_filename = NULL;
ClientContext ctx;
ClientContext ctx_wifi;
struct sockaddr_storage local_addr_prepare;


typedef struct st_sample_client_stream_ctx_t {
    struct st_sample_client_stream_ctx_t* next_stream;
    size_t file_rank;
    uint64_t stream_id;
    size_t name_length;
    size_t name_sent_length;
    FILE* F;
    size_t bytes_received;
    uint64_t remote_error;
    unsigned int is_name_sent : 1;
    unsigned int is_file_open : 1;
    unsigned int is_stream_reset : 1;
    unsigned int is_stream_finished : 1;
} sample_client_stream_ctx_t;


static void print_ip_port(struct sockaddr_storage *peer_addr) 
{
    char ip_str[INET6_ADDRSTRLEN]; // 足够存储 IPv4 或 IPv6 地址
    int port;

    if (peer_addr->ss_family == AF_INET) {
        // IPv4
        struct sockaddr_in *addr = (struct sockaddr_in *)peer_addr;
        inet_ntop(AF_INET, &(addr->sin_addr), ip_str, sizeof(ip_str));
        port = ntohs(addr->sin_port);
        printf("-----IPv4\n");
    } else if (peer_addr->ss_family == AF_INET6) {
        // IPv6
        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)peer_addr;
        inet_ntop(AF_INET6, &(addr->sin6_addr), ip_str, sizeof(ip_str));
        port = ntohs(addr->sin6_port);
        printf("-----IPv6\n");
    } else {
        strcpy(ip_str, "Unknown");
        printf("-----IPv6----------IPv4\n");
        port = 0;
    }

    printf("IP: %s, Port: %d\n", ip_str, port);
}

static int get_recv_buffer(void *recv_buffer, size_t bytes_recv, struct sockaddr_in* from_addr_, ClientContext *ctx_)
{
    if (bytes_recv <= 0)
    {
        printf("get_recv_buffer error\n");
        return 1;
    }

    uint64_t current_time = picoquic_current_time();
    int ret = picoquic_incoming_packet(ctx_->quic, (uint8_t *)recv_buffer, (size_t)bytes_recv, (struct sockaddr *)from_addr_, (struct sockaddr *)&(ctx_->local_addr), 0, 0, current_time);
    if (ret != 0)
    {
        printf("picoquic_incoming_packet error!\n");
        return 1;
    }
    /*
    printf("recv loacl:\n");
    print_ip_port((sockaddr_storage *)&(ctx.local_addr));
    printf("recv peer:\n");
    print_ip_port((sockaddr_storage *)from_addr_);
    */
    return 0;
}

static int get_send_buffer(void* send_buffer_, size_t& send_length_, struct sockaddr_storage* peer_addr_, ClientContext *ctx_, int& if_index)
{
    size_t send_length = 0;
    size_t* send_msg_ptr = nullptr;
    picoquic_connection_id_t log_cid;
    // struct sockaddr_storage peer_addr;
    struct sockaddr_storage local_addr;
    picoquic_cnx_t* last_cnx = nullptr;
    uint64_t current_time = picoquic_current_time();
    //如果没有待发包，ret返回0，send_length也为0
    //local_addr_prepare这个变量可以是全局，也可以是局部，但其他函数传进来的就崩溃
    //picoquic_default_select_next_path_mp() sender.c中函数，它默认轮训选择启用的多个路径
    int ret = picoquic_prepare_next_packet_ex(ctx_->quic, current_time, (uint8_t *)send_buffer_, 1536, &send_length, peer_addr_, &local_addr_prepare, &if_index, &log_cid, &last_cnx, send_msg_ptr);
    if(ret != 0)
    {
        printf("picoquic_prepare_next_packet_ex error!\n");
        return 1;
    }
    send_length_ = send_length;
    if(send_length_ != 0)
    {
        // printf("--------------------------------------if_index:%d\n", if_index);
        // printf("sendto loacl:\n");
        // print_ip_port(&local_addr_prepare);
        // printf("sendto peer:\n");
        // print_ip_port(peer_addr_);
    }
    return 0;
}

// 接收数据的回调函数
static void recv_cb(EV_P_ ev_io *w, int revents) 
{
    ClientContext *ctx_ = static_cast<ClientContext*>(w->data);//废弃
    char buffer[BUFFER_SIZE];
    socklen_t addr_len = sizeof(ctx_->from_addr);

    ssize_t len = recvfrom(ctx_->sockfd, buffer, BUFFER_SIZE, 0,
                           (struct sockaddr*)&ctx_->from_addr, &addr_len);
    if (len < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) return; // 暂无数据
        perror("recvfrom failed");
        ev_break(EV_A_ EVBREAK_ALL);//TODO:
        return;
    }
    //--
    int ret = get_recv_buffer(buffer, len, &ctx_->from_addr, ctx_);
    if(ret != 0)
    {
        printf("get_recv_buffer error\n");
    }
    //++
    // buffer[len] = '\0';
    std::cout << "Received from server length: " << len << std::endl;
}

int temp_int = 0;
// 发送数据的回调函数（定时器触发）
static void send_cb(EV_P_ ev_timer *w, int revents) {
    ClientContext *ctx_ = static_cast<ClientContext*>(w->data);
    std::string str_send = "from client:" +  std::to_string(temp_int++);
        picoquic_add_to_stream(ctx_->cnx, ctx_->stream_id, (const uint8_t*)str_send.c_str(), 100, 0);//0不是fin 100字节
        //picoquic_add_to_stream_ext()

    std::string message = "Hello from client at " + std::to_string(time(nullptr));
    ssize_t sent = 0;
    //--
    uint8_t send_buffer_[1536];
    size_t send_length_ = 0;
    struct sockaddr_storage peer_addr;
  
    int if_index = -2;
    int ret = get_send_buffer(send_buffer_, send_length_, &peer_addr, ctx_, if_index);
    if (ret != 0)
    {
        printf("get_send_buffer error ret:%d send_length_:%d \n", ret , send_length_);
        return;
        // exit(1);
    }
    if(send_length_ == 0) return;//没有保活
    int send_socket = 0;
    //第一次local_addr_prepare中没有值，这里让他走默认路径
    if(local_addr_prepare.ss_family != AF_INET || picoquic_compare_addr( (struct sockaddr*)&local_addr_prepare, (struct sockaddr*)&ctx_->local_addr ) == 0)
    {
        print_ip_port((sockaddr_storage*)(&local_addr_prepare));
        send_socket = ctx_->sockfd;
        // print_ip_port((sockaddr_storage*)(&peer_addr));
        printf("----------send_socket:%d  nb_paths:%d\n", send_socket, ctx_->cnx->nb_paths);
        
    }
    else
    {
        print_ip_port((sockaddr_storage*)(&local_addr_prepare));
        // print_ip_port((sockaddr_storage*)(&peer_addr));
        send_socket = ctx_wifi.sockfd;
        printf("++++++++++send_socket:%d  nb_paths:%d\n", send_socket, ctx_->cnx->nb_paths);
    }
    sent = sendto(send_socket, send_buffer_, send_length_, 0,
                          (struct sockaddr *)&peer_addr, sizeof(peer_addr));
    //++
    // ssize_t sent = sendto(ctx->sockfd, message.c_str(), message.size(), 0,
    //                       (struct sockaddr*)&ctx->server_addr, sizeof(ctx->server_addr));
    if (sent < 0) {
        perror("sendto failed");
        ev_break(EV_A_ EVBREAK_ALL);
        return;
    }
    std::cout << "Sent to server length: " << sent << "   all_len:" << send_length_ << std::endl;

}

static int open_socket(ClientContext& ctx_t, char* if_name, char* IP_t, uint16_t Port_t)
{
        // 创建 UDP socket
        ctx_t.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (ctx_t.sockfd < 0) {
            perror("socket creation failed");
            return 1;
        }
    
        // 设置 socket 为非阻塞
        int flags = fcntl(ctx_t.sockfd, F_GETFL, 0);
        fcntl(ctx_t.sockfd, F_SETFL, flags | O_NONBLOCK);
    
        // 绑定到本地 enp2s0 网卡
        // struct sockaddr_in local_addr;
        memset(&(ctx_t.local_addr), 0, sizeof(ctx_t.local_addr));
        ctx_t.local_addr.sin_family = AF_INET;
        // ctx.local_addr.sin_addr.s_addr = INADDR_ANY; // 绑定任意 IP CLIENT_IP
        ctx_t.local_addr.sin_addr.s_addr = inet_addr(IP_t); // 绑定任意 IP CLIENT_IP
        ctx_t.local_addr.sin_port = htons(Port_t);
    
        struct ifreq ifr;
        strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
        if (setsockopt(ctx_t.sockfd, SOL_SOCKET, SO_BINDTODEVICE, (char*)&ifr, sizeof(ifr)) < 0) {
            perror("SO_BINDTODEVICE failed");
            close(ctx_t.sockfd);
            return 1;
        }
    
        if (bind(ctx_t.sockfd, (struct sockaddr*)&(ctx_t.local_addr), sizeof(ctx_t.local_addr)) < 0) {
            perror("bind failed");
            close(ctx_t.sockfd);
            return 1;
        }
    
        // // 设置服务器地址
        // struct sockaddr_in server_addr;
        // memset(&ctx.server_addr, 0, sizeof(ctx.server_addr));
        // ctx.server_addr.sin_family = AF_INET;
        // ctx.server_addr.sin_port = htons(SERVER_PORT);
        // if (inet_pton(AF_INET, SERVER_IP, &ctx.server_addr.sin_addr) <= 0) {
        //     perror("inet_pton failed");
        //     close(ctx.sockfd);
        //     return 1;
        // }
    
        // ctx.sockfd = sockfd;
        // ctx.server_addr = server_addr;
        return 0;
}

int sample_client_callback(picoquic_cnx_t *cnx,
                           uint64_t stream_id, uint8_t *bytes, size_t length,
                           picoquic_call_back_event_t fin_or_event, void *callback_ctx, void *ctx_t)
{
    // ClientContext* ctx_ = (ClientContext*)ctx_t;
    switch (fin_or_event)
    {
    case picoquic_callback_stream_data:
    case picoquic_callback_stream_fin:
    {
        // printf("--picoquic_callback_stream_data--\n");
        // printf("--------receive:%s  receive id:%d\n", (char*)bytes, stream_id);
        // picoquic_close(cnx, 0); 如果什么都处理完了，则发送close帧，触发对方picoquic_callback_close事件
    }
        break;
    case picoquic_callback_stop_sending: /* Should not happen, treated as reset */
    {
        printf("--picoquic_callback_stop_sending--\n");
        picoquic_reset_stream(cnx, stream_id, 0);
    }
    case picoquic_callback_stream_reset: /* Server reset stream #x */
    {
        printf("--picoquic_callback_stream_reset--\n");
    }
        break;
    case picoquic_callback_stateless_reset:
    {
        printf("--picoquic_callback_stateless_reset--\n");
    }
    case picoquic_callback_close: /* Received connection close */
    {
        printf("--picoquic_callback_close--\n");
    }
    case picoquic_callback_application_close: /* Received application close */
    {
        printf("--picoquic_callback_application_close--\n");
        // picoquic_set_callback(cnx, nullptr, nullptr);
        // picoquic_close(ctx_->cnx, PICOQUIC_ERROR_MEMORY);
        // picoquic_free(ctx_->quic);//ctx.cnx也会在这里被释放
    }
        break;
    case picoquic_callback_version_negotiation:
                /* The client did not get the right version.
             * TODO: some form of negotiation?
             */
            fprintf(stdout, "Received a version negotiation request:");
            for (size_t byte_index = 0; byte_index + 4 <= length; byte_index += 4) {
                uint32_t vn = 0;
                for (int i = 0; i < 4; i++) {
                    vn <<= 8;
                    vn += bytes[byte_index + i];
                }
                fprintf(stdout, "%s%08x", (byte_index == 0) ? " " : ", ", vn);
            }
            fprintf(stdout, "\n");
            break;
    case picoquic_callback_stream_gap:
        break;
    case picoquic_callback_prepare_to_send:
    {
        printf("--picoquic_callback_prepare_to_send--\n");
        // picoquic_enable_keep_alive(ctx.cnx, 0);  在这里设置不起效果
    }
        break;

    case picoquic_callback_almost_ready:
    {
        printf("--picoquic_callback_almost_ready--\n");
    }
        break;

    case picoquic_callback_ready:
    {
        int ret = picoquic_probe_new_path(ctx_wifi.cnx, (struct sockaddr *)&ctx_wifi.server_addr, (struct sockaddr *)&(ctx_wifi.local_addr), picoquic_current_time());
        if (ret != 0)
        {
            printf("------picoquic_probe_new_path error\n");
        }
        printf("--picoquic_callback_ready--\n");
    }
        break;
    default:
        printf("--picoquic_callback_default--\n");
        /* unexpected -- just ignore. */
        break;
    }
    return 0; //只有返回0,流程才能正常执行下去。不如然源码调用认为应用层出错，就作处理比如picoquic_close()
}


static int init_picoquic()
{
    ticket_store_filename = PICOQUIC_SAMPLE_CLIENT_TICKET_STORE;
    uint64_t current_time = picoquic_current_time();
    ctx.quic = picoquic_create(1, NULL, NULL, NULL, PICOQUIC_SAMPLE_ALPN, NULL, NULL,
                               NULL, NULL, NULL, current_time, NULL,
                               ticket_store_filename, NULL, 0);
    if (ctx.quic == NULL)
    {
        fprintf(stderr, "Could not create quic context\n");
        return 1;
    }
    else
    {
        token_store_filename = PICOQUIC_SAMPLE_CLIENT_TOKEN_STORE;
        if (picoquic_load_retry_tokens(ctx.quic, token_store_filename) != 0)
        {
            fprintf(stderr, "No token file present. Will create one as <%s>.\n", token_store_filename);
        }
        picoquic_set_default_congestion_algorithm(ctx.quic, picoquic_bbr_algorithm);
        char const *qlog_dir = PICOQUIC_SAMPLE_CLIENT_QLOG_DIR;
        picoquic_set_key_log_file_from_env(ctx.quic);
        picoquic_set_qlog(ctx.quic, qlog_dir);
        picoquic_set_log_level(ctx.quic, 1);
        picoquic_set_cipher_suite(ctx.quic, 0); // default
        

        // ctx.quic->F_log = fopen("./picoquic.log", "a");
        // if (ctx.quic->F_log == NULL)
        // {
        //     fprintf(stderr, "Failed to open log file\n");
        // }
    }
    char const* sni = PICOQUIC_SAMPLE_SNI;
    //TODO:picoquic_null_connection_id
    int is_name = 0;
    //TOOD：server_addr在这里不能使用临时变量不然会崩块
    if (picoquic_get_server_address(SERVER_IP, SERVER_PORT, (sockaddr_storage *)&ctx.server_addr, &is_name) != 0)
    {
        printf("@@@@@@@@@@@@@@picoquic_get_server_address\n");
        exit(1);
    }

    if (picoquic_get_server_address(SERVER_IP, SERVER_PORT, (sockaddr_storage *)&ctx_wifi.server_addr, &is_name) != 0)
    {
        printf("@@@@@@@@@@@@@@picoquic_get_server_address wifi\n");
        exit(1);
    }

    ctx.cnx = picoquic_create_cnx(ctx.quic, picoquic_null_connection_id, picoquic_null_connection_id,
                                  (struct sockaddr *)&(ctx.server_addr), current_time, 0, sni, PICOQUIC_SAMPLE_ALPN, 1);
    if (ctx.cnx == NULL)
    {
        fprintf(stderr, "Could not create connection context\n");
        return 1;
    }
    ctx.cnx->local_parameters.enable_multipath = 1; //如果不启用多路径，新增路径就会把原来路径顶替掉
    picoquic_set_callback(ctx.cnx, sample_client_callback, &ctx);
    int ret = picoquic_start_client_cnx(ctx.cnx);
    if (ret < 0) {
        fprintf(stderr, "Could not activate connection\n");
        return 1;
    }

    //实测启作用，但其时间间隔的参数不启作用，因为正常api调用是在picoquic_packet_loop()控制sendto的发送频率,而我们手动调用sendto, 如果有包发送则保活包不会产生。
    // picoquic_enable_keep_alive(ctx.cnx, 100000); //为0则设为idle_timeout/2 当超过idle_timeout没有通信时，连接会断开。
    ctx.stream_id = picoquic_get_next_local_stream_id(ctx.cnx, 0); // 0为双向流 因为上下文是客户端的，因此会自动获取客户端的双向流
    /*
    0：客户端模式，双向流。
    1：服务器模式，双向流。
    2：客户端模式，单向流。
    3：服务器模式，单向流。
    */

    //使用picoquic_add_to_stream不需要激活
    // ret = picoquic_mark_active_stream(ctx.cnx, ctx.stream_id, 0, &ctx); //0表示非完全激活状态，可以自定义发送
    // if(ret != 0)
    // {
    //     printf("picoquic_mark_active_stream error!\n");
    //     return 1;
    // }

    return 0;
}

static int add_to_stream()
{
    return 0;
}




int main() {
    init_picoquic();
    printf("-=-=-=-=\n");
    if(open_socket(ctx, CLIENT_IF_NAME, CLIENT_IP, CLIENT_PORT) != 0)
    {
        printf("error! open_socket!\n");
        exit(1);
    }

    printf("-=-=-=-=\n");
    // 初始化 libev 事件循环
    struct ev_loop *loop = EV_DEFAULT;
    // 设置接收数据的 watcher
    ev_io_init(&ctx.io_watcher, recv_cb, ctx.sockfd, EV_READ);
    ctx.io_watcher.data = &ctx;
    ev_io_start(loop, &ctx.io_watcher);

    // 设置定时器，每 100ms 发送一次
    ev_timer_init(&ctx.timer_watcher, send_cb, 0.1, 0.1); // 初次延迟 100ms，之后每 100ms
    ctx.timer_watcher.data = &ctx;
    ev_timer_start(loop, &ctx.timer_watcher);


    std::cout << "Client started, bound to enp2s0, sending to " << SERVER_IP << ":" << SERVER_PORT << std::endl;

    if (open_socket(ctx_wifi, CLIENT_IF_NAME_WIFI, CLIENT_IP_WIFI, CLIENT_PORT_WIFI) != 0)
    {
        printf("error! open_socket!\n");
        exit(1);
    }
    ctx_wifi.cnx = ctx.cnx;
    ctx_wifi.quic = ctx.quic;

    ev_io_init(&ctx_wifi.io_watcher, recv_cb, ctx_wifi.sockfd, EV_READ);
    ctx_wifi.io_watcher.data = &ctx_wifi;
    ev_io_start(loop, &ctx_wifi.io_watcher);

    // 运行事件循环
    ev_run(loop, 0);

    // 清理
    //TODO:清理loop
    /*
        // Save tickets and tokens, and free the QUIC context 
        if (ctx.quic != NULL) {
            if (picoquic_save_session_tickets(ctx.quic, ticket_store_filename) != 0) {
                fprintf(stderr, "Could not store the saved session tickets.\n");
            }
            
            if (picoquic_save_retry_tokens(ctx.quic, token_store_filename) != 0) {
                fprintf(stderr, "Could not save tokens to <%s>.\n", token_store_filename);
            }
            picoquic_free(ctx.quic);
        }
    */
    close(ctx.sockfd);
    picoquic_free(ctx.quic);//ctx.cnx也会在这里被释放

    close(ctx_wifi.sockfd);
    picoquic_free(ctx_wifi.quic);//ctx.cnx也会在这里被释放
    return 0;
}

































